//! Represents a boundary between client and server code. Every boundary
//! gets bundled twice, once for the desired target, and once to generate
//! a module of "references". Specifically, the generated file takes the
//! canonical Ast as input to derive a wrapper. See `Framework.ServerComponents`
//! for more details about this generated file.
//!
//! This is sometimes abbreviated as SCB
use_directive: UseDirective,

/// The index of the original file.
source_index: Index.Int,

/// Index to the file imported on the opposite platform, which is
/// generated by the bundler. For client components, this is the
/// server's code. For server actions, this is the client's code.
reference_source_index: Index.Int,

/// When `bake.Framework.ServerComponents.separate_ssr_graph` is enabled this
/// points to the separated module. When the SSR graph is not separate, this is
/// equal to `reference_source_index`
//
// TODO: Is this used for server actions.
ssr_source_index: Index.Int,

/// The requirements for this data structure is to have reasonable lookup
/// speed, but also being able to pull a `[]const Index.Int` of all
/// boundaries for iteration.
pub const List = struct {
    list: std.MultiArrayList(ServerComponentBoundary) = .{},
    /// Used to facilitate fast lookups into `items` by `.source_index`
    map: Map = .{},

    const Map = std.ArrayHashMapUnmanaged(void, void, struct {}, true);

    /// Can only be called on the bundler thread.
    pub fn put(
        m: *List,
        allocator: std.mem.Allocator,
        source_index: Index.Int,
        use_directive: UseDirective,
        reference_source_index: Index.Int,
        ssr_source_index: Index.Int,
    ) !void {
        try m.list.append(allocator, .{
            .source_index = source_index,
            .use_directive = use_directive,
            .reference_source_index = reference_source_index,
            .ssr_source_index = ssr_source_index,
        });
        const gop = try m.map.getOrPutAdapted(
            allocator,
            source_index,
            Adapter{ .list = m.list.slice() },
        );
        bun.assert(!gop.found_existing);
    }

    /// Can only be called on the bundler thread.
    pub fn getIndex(l: *const List, real_source_index: Index.Int) ?usize {
        return l.map.getIndexAdapted(
            real_source_index,
            Adapter{ .list = l.list.slice() },
        );
    }

    /// Use this to improve speed of accessing fields at the cost of
    /// storing more pointers. Invalidated when input is mutated.
    pub fn slice(l: List) Slice {
        return .{ .list = l.list.slice(), .map = l.map };
    }

    pub const Slice = struct {
        list: std.MultiArrayList(ServerComponentBoundary).Slice,
        map: Map,

        pub fn getIndex(l: *const Slice, real_source_index: Index.Int) ?usize {
            return l.map.getIndexAdapted(
                real_source_index,
                Adapter{ .list = l.list },
            ) orelse return null;
        }

        pub fn getReferenceSourceIndex(l: *const Slice, real_source_index: Index.Int) ?u32 {
            const i = l.map.getIndexAdapted(
                real_source_index,
                Adapter{ .list = l.list },
            ) orelse return null;
            bun.unsafeAssert(l.list.capacity > 0); // optimize MultiArrayList.Slice.items
            return l.list.items(.reference_source_index)[i];
        }

        pub fn bitSet(scbs: Slice, alloc: std.mem.Allocator, input_file_count: usize) !bun.bit_set.DynamicBitSetUnmanaged {
            var scb_bitset = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(alloc, input_file_count);
            for (scbs.list.items(.source_index)) |source_index| {
                scb_bitset.set(source_index);
            }
            return scb_bitset;
        }
    };

    pub const Adapter = struct {
        list: std.MultiArrayList(ServerComponentBoundary).Slice,

        pub fn hash(_: Adapter, key: Index.Int) u32 {
            return std.hash.int(@as(u32, key));
        }

        pub fn eql(adapt: Adapter, a: Index.Int, _: void, b_index: usize) bool {
            bun.unsafeAssert(adapt.list.capacity > 0); // optimize MultiArrayList.Slice.items
            return a == adapt.list.items(.source_index)[b_index];
        }
    };
};

const bun = @import("bun");
const std = @import("std");

const js_ast = bun.ast;
const Index = js_ast.Index;
const ServerComponentBoundary = js_ast.ServerComponentBoundary;
const UseDirective = js_ast.UseDirective;
